

#include "appinc.h"



static int socket_listen(const char *addr, uint16_t port, int nonblock, int tcp_num)
{
	int ret, sockfd, flags, addrsize;
	uint8_t addrbuf[128] = {0};
	struct sockaddr *saddr = (struct sockaddr *)addrbuf;
	
	if (port == 0) {
		struct sockaddr_un *addr_un = (struct sockaddr_un *)addrbuf;
		
		addr_un->sun_family = AF_LOCAL;
		if (addr[0] == '\0')
			strlcpy(addr_un->sun_path+1, addr+1, sizeof(addr_un->sun_path)-1);
		else {
			unlink(addr);
			strlcpy(addr_un->sun_path, addr, sizeof(addr_un->sun_path));
		}
		addrsize = sizeof(*addr_un);
	}else {
		struct sockaddr_in *addr_in = (struct sockaddr_in *)addrbuf;
	
		addr_in->sin_family = AF_INET;
		addr_in->sin_port = htons(port);
		if (NULL == addr) {
			addr_in->sin_addr.s_addr = htonl(INADDR_ANY);
		} else {
			ret = inet_pton(AF_INET, addr, &addr_in->sin_addr);
			if (ret <= 0)
				return -1;
		}
		addrsize = sizeof(*addr_in);
	}
	if (tcp_num > 0)
		sockfd = socket(saddr->sa_family, SOCK_STREAM, 0);
	else
		sockfd = socket(saddr->sa_family, SOCK_DGRAM, 0);
	if (sockfd < 0)
		return sockfd;
	
	if (nonblock) {
		ret = libfd_set_nonblock(sockfd, true);
		if (ret < 0) {
			close(sockfd);
			return ret;
		}
	}
	flags = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));

	ret = bind(sockfd, saddr, addrsize);
	if (ret == 0 && tcp_num > 0) 
		ret = listen(sockfd, tcp_num);
	if (ret < 0) {
		close(sockfd);
		return ret;
	}
	
	return sockfd;
}

static int socket_connect_timeout(int socket, struct sockaddr *saddr, int addrsize, int timeout)
{
	int ret;
	
	if (timeout > 0)
		libfd_set_nonblock(socket, true);
	
	ret = connect(socket, saddr, addrsize);
	if (ret == 0)
		return 0;
	if (timeout > 0 && errno == EINPROGRESS) {
		fd_set wset;
		struct timeval timeo;
		
		FD_ZERO(&wset);
		FD_SET(socket, &wset);
		timeo.tv_sec = timeout/1000;
		timeo.tv_usec = (timeout%1000)*1000;
		ret = select(socket + 1, NULL, &wset, NULL, &timeo);
		if (ret > 0) {
			libfd_set_nonblock(socket, false);
			return 0;
		}
	}

	return -1;
}


static int socket_connect(const char *addr, const char *port, int istcp, int timeout)
{
	int ret, type, sockfd = 0, addrsize;
	uint8_t addrbuf[128] = {0};
	struct sockaddr *saddr = (struct sockaddr *)addrbuf;
	struct addrinfo *ainfo, *rp;

	if (istcp)
		type = SOCK_STREAM;
	else
		type = SOCK_DGRAM;

	if (port == NULL) {
		//连接unix域
		struct sockaddr_un *addr_un = (struct sockaddr_un *)addrbuf;
		
		addr_un->sun_family = AF_LOCAL;
		if (addr[0] == '\0')
			strlcpy(addr_un->sun_path+1, addr+1, sizeof(addr_un->sun_path)-1);
		else
			strlcpy(addr_un->sun_path, addr, sizeof(addr_un->sun_path));
		addrsize = sizeof(*addr_un);
	} else if (libstr_is_ip(addr) && libstr_is_digit(port)) {
		/* 通过数据形式的IP的PORT连接 */
		struct sockaddr_in *addr_in = (struct sockaddr_in *)addrbuf;
		
		addr_in->sin_family = AF_INET;
		addr_in->sin_port = htons(atoi(port));
		ret = inet_pton(AF_INET, addr, &addr_in->sin_addr);
		if (ret < 0)
			return ret;
		addrsize = sizeof(*addr_in);
	} else {
		ret = getaddrinfo(addr, port, NULL, &ainfo);
		if (ret) 
			return -1;

		for (rp = ainfo; rp != NULL; rp = rp->ai_next) {
			if (rp->ai_socktype != type)
				continue;
			sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
			if (sockfd < 0)
				continue;
			
			ret = socket_connect_timeout(sockfd, rp->ai_addr, rp->ai_addrlen, timeout);
			if (ret == 0)
				break;

			close(sockfd);
			sockfd = -1;
		}
		freeaddrinfo(ainfo);
		if (sockfd < 0)
			return sockfd;
	}

	if (sockfd == 0) {
		sockfd = socket(saddr->sa_family, type, 0);
		if (sockfd < 0)
			return sockfd;
		ret = socket_connect_timeout(sockfd, saddr, addrsize, timeout);
		if (ret < 0) {
			close(sockfd);
			return ret;
		}
	}

	return sockfd;
}


int libsocket_listen_tcp(const char *addr, uint16_t port, int nonblock, int num)
{
	if (addr == NULL && port == 0)
		return -1;
	
	if (num <= 0 || num > 1024)
		num = 1024;

	return socket_listen(addr, port, nonblock, num);
}



int libsocket_listen_udp(const char *addr, uint16_t port, int nonblock)
{
	if (addr == NULL && port == 0)
		return -1;
	
	return socket_listen(addr, port, nonblock, 0);
}



int libsocket_connect_tcp(const char *addr, const char *port, int timeout)
{
	if (addr == NULL)
		return -1;

	return socket_connect(addr, port, 1, timeout);
}



int libsocket_connect_udp(const char *addr, const char *port, int timeout)
{
	if (addr == NULL)
		return -1;

	return socket_connect(addr, port, 0, timeout);
}


